import getopt
import os
import subprocess
import sys
import time


class sampling:

    def __init__(self):
        self.highlight = '\033[7m'
        self.end = "\033[0m"
        self.pid = 0
        self.tids = ''
        self.interval = 1
        self.count = 0
        self.limit = 5
        self.force= False
        self.deadlock = False
        self.blocking= False
        self.options()

    def setEnv(self):
        JAVA_HOME = '/home/ttx/app/jre'
        PATH = "%s/bin:%s" % (JAVA_HOME, os.environ['PATH'])
        CLASSPATH = ".:%s/lib/dt.jar:%s/lib/tools.jar" % (JAVA_HOME, JAVA_HOME)
        os.environ['JAVA_HOME'] = JAVA_HOME
        os.environ['PATH'] = PATH
        os.environ['CLASSPATH'] = CLASSPATH

    def findHeadTidByPid(self):
        cmd = "ps %s -wwLo pid,lwp,pcpu,user --sort -pcpu --no-headers | head -n %s" %("-p %s"%self.pid,self.limit)
        ret,out,err = self.run_cmd(cmd)
        if ret ==0:
            pss = self.buildTidDictByPs(out)
            ret, out, err = self.getStackThread()
            if ret == 0:
                self.group(out, pss)
            else:
                print("jstack执行错误%s"%err)
        else:
            print("执行命令错误:%s"%err)

    def findHeadTidByTop(self):
        cmd = "top -H -p %s -b -d 0.5 -n 1" % self.pid
        ret,out,err = self.run_cmd(cmd)
        if ret ==0:
            pss = self.buildTidDictByTop(out)
            ret, out, err =self.getStackThread()
            if ret == 0:
                self.group(out, pss)
            else:
                print("jstack执行错误%s"%err)
        else:
            print("执行命令错误:%s"%err)

    def buildTidDictByPs(self,out):
        pss = []
        for tidd in filter(None,out.decode('UTF-8').split("\n")):
            tids = []
            for td in tidd.split(" "):
                if td == "":
                    continue
                tids.append(td)
            d = {'pid':tids[0],"threadId":tids[1],"pcpu":tids[2],"user":tids[3]}
            pss.append(Dict2Obj(d))
        return pss

    def buildTidDictByTop(self,out):
        pss = []
        for tidd in filter(None, out.decode('UTF-8').split("COMMAND")[1].split("\n")[0:int(self.limit)+1]):
            td = tidd.split(" ")
            d = {'pid': self.pid, "threadId": td[0], "pcpu": td[18], "user": td[1]}
            pss.append(Dict2Obj(d))
        return pss

    def getStackThread(self):
        self.setEnv()
        if self.force:
            cmd = "jstack -F -l %s"%self.pid
        else:
            cmd = "jstack -l %s"%self.pid
        return self.run_cmd(cmd)

    def group(self,out,pss):
        jstacks = out.decode('UTF-8').split("\n\n")
        index = 0
        for ps in pss:
            index = index + 1
            if ps.pid:
                print('%s【%s】cpu:%s  pid:%s  tid: %s  nid:%s  user:%s  stack：%s' %(self.highlight,index,ps.pcpu,ps.pid,ps.threadId,hex(int(ps.threadId)),ps.user,self.end))
            flag = self.singleStack(jstacks,ps.threadId)
            if not flag:
                print("wow! 没扣到...")
            print("\n")

    def singleStack(self,jstacks,tid):
        flag = 0
        for value in jstacks:
            if value.find(hex(int(tid))) > 0:
                if (jstacks[jstacks.index(value) + 1]).find("nid") < 0:
                    print("\n".join([value, jstacks[jstacks.index(value) + 1]]))
                else:
                    print(value)
                flag = 1
                break
        return flag


    def blockingThread(self):
        blocks = []
        ret, out, err = self.getStackThread()
        if ret == 0:
            jstacks = out.decode('UTF-8').split("\n\n")
            for value in jstacks:
                if value.find("Blocked") > 0:
                    blocks.append(value)
            if len(blocks) > 0:
                print("\033[1;31m总阻塞%s线程，列出前5个阻塞线程\033[0m"%len(blocks))
                le = 5 if len(blocks) >5 else len(blocks)
                for i in range[0,le]:
                    print("【%】"%(i+1))
                    print(blocks[i])
                    print("\n")
            else:
                print("\033[1;32m没有发现阻塞线程\033[0m")
        else:
            print("jstack执行错误%s" % err)


    def deadlockThread(self):
        deadlock = []
        ret, out, err = self.getStackThread()
        if ret == 0:
            jstacks = out.decode('UTF-8').split("\n\n")
            for value in jstacks:
                if value.find("Blocked") > 0:
                    deadlock.append(value)
            if len(deadlock) > 0:
                print("\033[1;31m总死锁%s线程，列出前5个死锁线程\033[0m"%len(deadlock))
                le = 5 if len(deadlock) >5 else len(deadlock)
                for i in range[0,le]:
                    print("【%】"%(i+1))
                    print(deadlock[i])
                    print("\n")
            else:
                print("\033[1;32m没有发现死锁线程\033[0m")
        else:
            print("jstack执行错误%s" % err)


    def run_cmd(self,cmd):
        child = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
        out, err = child.communicate()
        ret = child.wait()
        return (ret, out, err)


    def options(self):
        opts, args = getopt.getopt(sys.argv[1:], "p:t:c:i:n:bdF", ["pid=tid="])
        for op, value in opts:
            if op in ("-p","-pid"):
                self.pid = value
            elif op in ("-t","-tid"):
                self.tids = value
            elif op in ("-c"):
                self.count =int(value)
            elif op in ("-i"):
                self.interval= int(value)
            elif op in ("-n"):
                self.limit = int(value)
            elif op in ("-F"):
                self.force = True
            elif op in ("-b"):
                self.blocking = True
            elif op in ("-d"):
                self.deadlock = True
        if self.pid == 0 and len(args) > 0:
            self.pid = args[0]
        self.validateOptions()

    def validateOptions(self):
        if self.pid ==0:
            print("pid未指定")
            self.usage()
            sys.exit()

    def usage(self):
        print("用法：python3 %s [-p|-pid][-t:tid][-c][-i][-n][-F] <pid>" % os.path.basename(__file__))
        print('-p|-pid：进程ID')
        print('-t|-tid：线程ID')
        print('-c：执行次数')
        print('-i：执行多次间隔时间秒,默认1')
        print('-n：使用PID采样时打印多少条线程栈，默认5')
        print("""eg:
            1. Python3 ttc.py 29273     #打印29273进程cpu前5线程栈
            2. Python3 ttc.py -n 8 29273    #打印29273进程CPU前8线程栈
            3. Python3 ttc.py -c 3 29273    #打印29273进程，执行3次，每次间隔1秒
            4. python3 ttc.py -c 3 -i 2 -n 4 29273  #打印29273进程，执行3次，间隔2秒 ，cpu前4线程栈  
            5. python3 ttc.py -t 29999,28888  29373    #打印29273进程的29999，28888线程的线程栈
            6. Python3 ttc.py -F 29273   #强制打印,用于JVM不理睬你的时候
            7. Python3 ttc.py -b 29273   #统计是否阻塞线程，默认打印5个
            8. Python3 ttc.py -d 29273   #统计是否死锁线程，默认打印5个
        """)

    def process(self):
        if self.blocking:
            self.blockingThread()
        elif self.deadlock:
            self.deadlockThread()
        elif self.tids:
            pss =[]
            for tid in  self.tids.split(","):
                d = {'pid': self.pid, "threadId": tid,"pcpu":'-',"user":'-'}
                pss.append(Dict2Obj(d))
            self.getStackThread(pss)
        else:
            if  self.interval and   self.count > 0:
                for i in range(0,self.count):
                    print("=========================================================")
                    print("=========================\033[1;35m第%s次\033[0m========================"%(i+1))
                    print("=========================================================")
                    print("\n")
                    self.findHeadTidByTop()
                    time.sleep(self.interval)
            else:
                self.findHeadTidByTop()


class Dict2Obj(dict):
    def __init__(self, *args, **kwargs):
        super(Dict2Obj, self).__init__(*args, **kwargs)

    def __getattr__(self, key):
        value = self[key]
        if isinstance(value, dict):
            value = Dict2Obj(value)
        return value
sampling = sampling()
sampling.process()
